﻿using Mono.Cecil.Cil;

namespace il.lua;

public static class Mixin
{
    public static bool IsVoid(this TypeReference type)
    {
        while (type is OptionalModifierType || type is RequiredModifierType)
            type = ((TypeSpecification)type).ElementType;
        return type.MetadataType == MetadataType.Void;
    }

    public static void RenameParameters(this MethodBody body)
    {
        if (body == null)
            return;
        var parameters = body.Method.Parameters;
        for (var i = 0; i < parameters.Count; i++)
        {
            var name = parameters[i].Name;
            if (string.IsNullOrEmpty(name))
                name = $"__arg{i}__";
        }

        if (body.ThisParameter != null)
            body.ThisParameter.Name = "__self__";
    }

    public static string GetVariableName(this MethodDefinition method, VariableDefinition variable)
    {
        var symbolReader = method.Module.SymbolReader;
        string name = null;
        if (symbolReader != null)
        {
            var debugInfo = symbolReader.Read(method);
            debugInfo?.TryGetName(variable, out name);
        }
        if (string.IsNullOrEmpty(name))
            name = $"__var{variable.Index}__";
        return name;
    }

    public static ParameterDefinition GetParameter(this MethodBody self, int index)
    {
        MethodDefinition method = self.Method;
        ParameterDefinition parameter;
        if (method.HasThis)
        {
            if (index == 0)
            {
                parameter = self.ThisParameter;
                if (string.IsNullOrEmpty(parameter.Name))
                    parameter.Name = "__self__";
                return parameter;
            }

            checked
            {
                --index;
            }
        }

        var parameters = method.Parameters;
        parameter = index < 0 || index >= parameters.Count ? (ParameterDefinition)null : parameters[index];
        if (string.IsNullOrEmpty(parameter.Name))
            parameter.Name = $"__param{parameter.Index}__";
        return parameter;
    }

    public static void Simplify(this Instruction instruction, MethodBody methodBody)
    {
        var code = instruction.OpCode.Code;
        var operand = instruction.Operand;
        switch (code)
        {
            case Code.Ldarg_0:
                instruction.OpCode = OpCodes.Ldarg;
                operand = (object)methodBody.GetParameter(0);
                break;
            case Code.Ldarg_1:
                instruction.OpCode = OpCodes.Ldarg;
                operand = (object)methodBody.GetParameter(1);
                break;
            case Code.Ldarg_2:
                instruction.OpCode = OpCodes.Ldarg;
                operand = (object)methodBody.GetParameter(2);
                break;
            case Code.Ldarg_3:
                instruction.OpCode = OpCodes.Ldarg;
                operand = (object)methodBody.GetParameter(3);
                break;
            case Code.Ldloc_0:
                instruction.OpCode = OpCodes.Ldloc;
                operand = (object)methodBody.Variables[0];
                break;
            case Code.Ldloc_1:
                instruction.OpCode = OpCodes.Ldloc;
                operand = (object)methodBody.Variables[1];
                break;
            case Code.Ldloc_2:
                instruction.OpCode = OpCodes.Ldloc;
                operand = (object)methodBody.Variables[2];
                break;
            case Code.Ldloc_3:
                instruction.OpCode = OpCodes.Ldloc;
                operand = (object)methodBody.Variables[3];
                break;
            case Code.Stloc_0:
                instruction.OpCode = OpCodes.Stloc;
                operand = (object)methodBody.Variables[0];
                break;
            case Code.Stloc_1:
                instruction.OpCode = OpCodes.Stloc;
                operand = (object)methodBody.Variables[1];
                break;
            case Code.Stloc_2:
                instruction.OpCode = OpCodes.Stloc;
                operand = (object)methodBody.Variables[2];
                break;
            case Code.Stloc_3:
                instruction.OpCode = OpCodes.Stloc;
                operand = (object)methodBody.Variables[3];
                break;
            case Code.Ldarg_S:
                instruction.OpCode = OpCodes.Ldarg;
                break;
            case Code.Ldarga_S:
                instruction.OpCode = OpCodes.Ldarga;
                break;
            case Code.Starg_S:
                instruction.OpCode = OpCodes.Starg;
                break;
            case Code.Ldloc_S:
                instruction.OpCode = OpCodes.Ldloc;
                break;
            case Code.Ldloca_S:
                instruction.OpCode = OpCodes.Ldloca;
                break;
            case Code.Stloc_S:
                instruction.OpCode = OpCodes.Stloc;
                break;
            case Code.Ldc_R4:
                instruction.OpCode = OpCodes.Ldc_R8;
                operand = (object)(double)(float)operand;
                break;
            case Code.Ldc_I4_M1:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)-1l;
                break;
            case Code.Ldc_I4_0:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)0l;
                break;
            case Code.Ldc_I4_1:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)1l;
                break;
            case Code.Ldc_I4_2:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)2l;
                break;
            case Code.Ldc_I4_3:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)3l;
                break;
            case Code.Ldc_I4_4:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)4l;
                break;
            case Code.Ldc_I4_5:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)5l;
                break;
            case Code.Ldc_I4_6:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)6l;
                break;
            case Code.Ldc_I4_7:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)7l;
                break;
            case Code.Ldc_I4_8:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)8l;
                break;
            case Code.Ldc_I4_S:
                instruction.OpCode = OpCodes.Ldc_I8;
                operand = (object)(long)(sbyte)operand;
                break;
            case Code.Br_S:
                instruction.OpCode = OpCodes.Br;
                break;
            case Code.Brfalse_S:
                instruction.OpCode = OpCodes.Brfalse;
                break;
            case Code.Brtrue_S:
                instruction.OpCode = OpCodes.Brtrue;
                break;
            case Code.Beq_S:
                instruction.OpCode = OpCodes.Beq;
                break;
            case Code.Bge_S:
                instruction.OpCode = OpCodes.Bge;
                break;
            case Code.Bgt_S:
                instruction.OpCode = OpCodes.Bgt;
                break;
            case Code.Ble_S:
                instruction.OpCode = OpCodes.Ble;
                break;
            case Code.Blt_S:
                instruction.OpCode = OpCodes.Blt;
                break;
            case Code.Bne_Un_S:
                instruction.OpCode = OpCodes.Bne_Un;
                break;
            case Code.Bge_Un_S:
                instruction.OpCode = OpCodes.Bge_Un;
                break;
            case Code.Bgt_Un_S:
                instruction.OpCode = OpCodes.Bgt_Un;
                break;
            case Code.Ble_Un_S:
                instruction.OpCode = OpCodes.Ble_Un;
                break;
            case Code.Blt_Un_S:
                instruction.OpCode = OpCodes.Blt_Un;
                break;
            case Code.Ldind_I1:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.SByte;
                break;
            case Code.Ldind_U1:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Byte;
                break;
            case Code.Ldind_I2:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int16;
                break;
            case Code.Ldind_U2:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.UInt16;
                break;
            case Code.Ldind_I4:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int32;
                break;
            case Code.Ldind_U4:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.UInt32;
                break;
            case Code.Ldind_I8:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int64;
                break;
            case Code.Ldind_I:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.IntPtr;
                break;
            case Code.Ldind_R4:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Single;
                break;
            case Code.Ldind_R8:
                instruction.OpCode = OpCodes.Ldobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Double;
                break;
            case Code.Stind_I1:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Byte;
                break;
            case Code.Stind_I2:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int16;
                break;
            case Code.Stind_I4:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int32;
                break;
            case Code.Stind_I8:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Int64;
                break;
            case Code.Stind_R4:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Single;
                break;
            case Code.Stind_R8:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.Double;
                break;
            case Code.Leave_S:
                instruction.OpCode = OpCodes.Leave;
                break;
            case Code.Stind_I:
                instruction.OpCode = OpCodes.Stobj;
                operand = (object)methodBody.Method.Module.TypeSystem.IntPtr;
                break;
        }

        instruction.Operand = operand;
    }
}